package com.xhj.mybatis.executor.resultset;

import com.xhj.mybatis.io.Resources;
import com.xhj.mybatis.mapping.ResultMap;
import com.xhj.mybatis.session.Configuration;
import com.xhj.mybatis.type.*;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;

/**
 * ResultSetWrapper
 *
 * @author XJks
 * @description 结果集包装器，用于封装 JDBC 的 ResultSet，并对查询结果的元数据信息（如列名、列类型、Java 类名等）进行缓存和处理。它还负责根据列名和属性类型选择合适的 TypeHandler，实现数据库结果到 Java 对象的高效映射。
 */
public class ResultSetWrapper {

    /**
     * 保存当前的 ResultSet 对象，用于操作数据库查询结果
     */
    private final ResultSet resultSet;

    /**
     * MyBatis 的类型处理器注册表，用于根据 Java 类型和 JDBC 类型选择合适的 TypeHandler
     */
    private final TypeHandlerRegistry typeHandlerRegistry;

    /**
     * 保存所有列的名称
     */
    private final List<String> columnNames = new ArrayList<>();

    /**
     * 保存所有列对应的 Java 类名
     */
    private final List<String> classNames = new ArrayList<>();

    /**
     * 保存所有列对应的 JDBC 类型
     */
    private final List<JdbcType> jdbcTypes = new ArrayList<>();

    /**
     * 缓存每个列名和属性类型对应的 TypeHandler，避免重复查找
     */
    private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();

    /**
     * 缓存每个 ResultMap 和前缀对应的已映射列名
     */
    private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();

    /**
     * 缓存每个 ResultMap 和前缀对应的未映射列名
     */
    private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();

    /**
     * 构造方法，初始化 ResultSetWrapper
     *
     * @param rs            数据库查询结果集
     * @param configuration MyBatis 配置对象
     * @throws SQLException 可能抛出的 SQL 异常
     */
    public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
        super();
        // 获取类型处理器注册表
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        // 保存 ResultSet
        this.resultSet = rs;
        // 获取结果集的元数据（列信息）
        final ResultSetMetaData metaData = rs.getMetaData();
        // 获取列数
        final int columnCount = metaData.getColumnCount();
        // 遍历每一列，收集列名、JDBC 类型、Java 类名
        for (int i = 1; i <= columnCount; i++) {
            // 根据配置决定用列标签还是列名
            columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
            // 获取 JDBC 类型
            jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
            // 获取 Java 类名
            classNames.add(metaData.getColumnClassName(i));
        }
    }

    /**
     * 获取当前 ResultSet
     */
    public ResultSet getResultSet() {
        return resultSet;
    }

    /**
     * 获取所有列名
     */
    public List<String> getColumnNames() {
        return this.columnNames;
    }

    /**
     * 获取所有列的 Java 类名（只读）
     */
    public List<String> getClassNames() {
        return Collections.unmodifiableList(classNames);
    }

    /**
     * 获取所有列的 JDBC 类型
     */
    public List<JdbcType> getJdbcTypes() {
        return jdbcTypes;
    }

    /**
     * 根据列名获取该列的 JDBC 类型
     *
     * @param columnName 列名
     * @return 对应的 JDBC 类型
     */
    public JdbcType getJdbcType(String columnName) {
        for (int i = 0; i < columnNames.size(); i++) {
            // 忽略大小写比较列名
            if (columnNames.get(i).equalsIgnoreCase(columnName)) {
                return jdbcTypes.get(i);
            }
        }
        return null;
    }

    /**
     * 获取指定属性类型和列名对应的 TypeHandler
     *
     * @param propertyType Java 属性类型
     * @param columnName   列名
     * @return 对应的 TypeHandler
     */
    public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
        // 定义一个变量用于存放类型处理器
        TypeHandler<?> handler = null;
        // 从缓存中获取该列名对应的类型处理器映射（key 是属性类型，value 是 TypeHandler）
        Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
        if (columnHandlers == null) {
            // 如果缓存中没有该列名的映射，则新建一个映射
            columnHandlers = new HashMap<>();
            // 并放入缓存，方便下次直接使用
            typeHandlerMap.put(columnName, columnHandlers);
        } else {
            // 如果缓存中有该列名的映射，则直接获取该属性类型对应的 TypeHandler
            handler = columnHandlers.get(propertyType);
        }
        if (handler == null) {
            // 如果还没有找到 TypeHandler，则根据属性类型和 JDBC 类型查找
            JdbcType jdbcType = getJdbcType(columnName);
            handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
            // 如果找不到或找到的是未知类型处理器，则尝试用列的 Java 类型再查找
            if (handler == null || handler instanceof UnknownTypeHandler) {
                // 获取该列名在所有列名中的索引
                final int index = columnNames.indexOf(columnName);
                // 通过类名字符串加载 Java 类型
                final Class<?> javaType = resolveClass(classNames.get(index));
                if (javaType != null && jdbcType != null) {
                    // 如果 Java 类型和 JDBC 类型都不为空，则用它们查找 TypeHandler
                    handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
                } else if (javaType != null) {
                    // 只用 Java 类型查找
                    handler = typeHandlerRegistry.getTypeHandler(javaType);
                } else if (jdbcType != null) {
                    // 只用 JDBC 类型查找
                    handler = typeHandlerRegistry.getTypeHandler(jdbcType);
                }
            }
            // 如果还找不到合适的 TypeHandler，则使用默认的 ObjectTypeHandler
            if (handler == null || handler instanceof UnknownTypeHandler) {
                handler = new ObjectTypeHandler();
            }
            // 将查找到的 TypeHandler 缓存起来，避免下次重复查找
            columnHandlers.put(propertyType, handler);
        }
        // 返回最终找到的 TypeHandler
        return handler;
    }

    /**
     * 根据类名字符串加载 Java 类
     *
     * @param className 类名字符串
     * @return Java 类对象，找不到返回 null
     */
    private Class<?> resolveClass(String className) {
        try {
            // className 可能为 null
            if (className != null) {
                return Resources.classForName(className);
            }
        } catch (ClassNotFoundException e) {
            // 找不到类时忽略异常
        }
        return null;
    }

    /**
     * 加载并缓存已映射和未映射的列名
     *
     * @param resultMap    MyBatis 的 ResultMap，描述了对象属性和数据库列的映射关系
     * @param columnPrefix 列名前缀（用于区分多表查询时的列名）
     * @throws SQLException SQL 异常
     */
    private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
        // 创建一个列表，用于存放已映射的列名
        List<String> mappedColumnNames = new ArrayList<>();
        // 创建一个列表，用于存放未映射的列名
        List<String> unmappedColumnNames = new ArrayList<>();
        // 如果有前缀，将前缀转换为大写（用于后续比较，忽略大小写）
        final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
        /**
         * TODO:
         *  1.resultMap.getMappedColumns() 获取 ResultMap 中所有已映射的列名集合 {@link ResultMap#mappedColumns}，该属性是在 XxxMapper.xml 文件中 <resultMap> 标签中的 <id>、<result>、<association>、<collection> 等标签中定义的列名。
         *  2.prependPrefixes 方法会给这些列名加上前缀（如果有的话），并返回加前缀后的列名集合，存放在 mappedColumns 变量中。
         *  3.在下方的 for 循环中，会遍历 ResultSetWrapper 中的所有列名（{@link ResultSetWrapper#columnNames} 列表），这些列名是从数据库查询结果的元数据中获取的。
         *  4.对于 {@link ResultSetWrapper#columnNames} 中的每一个列名，会将其转换为大写形式（upperColumnName），然后检查该列名（大写形式）是否在上一步得到的已映射列名集合（mappedColumns）中。
         *  5.如果在集合中，说明该列名已经在 ResultMap 中定义了映射关系，就将其加入到 mappedColumnNames 列表中（存储大写形式的列名）。否则，说明该列名没有映射关系，就将其加入到 unmappedColumnNames 列表中（保留原始列名）。
         *  6.最后，会将这两个列表分别缓存到 ResultSetWrapper 的 {@link ResultSetWrapper#mappedColumnNamesMap} 和 {@link ResultSetWrapper#unMappedColumnNamesMap} 属性中，key 是 ResultMap 和前缀的组合字符串，value 分别是已映射和未映射的列名列表。
         */
        final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
        // 遍历所有查询结果中的列名
        for (String columnName : columnNames) {
            // 将当前列名转换为大写，便于和已映射列名集合比较
            final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
            // 判断当前列名（大写）是否在已映射列名集合中
            if (mappedColumns.contains(upperColumnName)) {
                // 如果已映射，则加入已映射列表（存大写形式）
                mappedColumnNames.add(upperColumnName);
            } else {
                // 如果未映射，则加入未映射列表（保留原始列名）
                unmappedColumnNames.add(columnName);
            }
        }
        // 将已映射列名列表缓存到 mappedColumnNamesMap，key 为 ResultMap 和前缀的组合
        mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
        // 将未映射列名列表缓存到 unMappedColumnNamesMap，key 同上
        unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
    }

    /**
     * 获取已映射的列名列表
     *
     * @param resultMap    ResultMap，描述对象属性和数据库列的映射关系
     * @param columnPrefix 列名前缀（用于多表查询时区分不同表的列名）
     * @return 已映射列名列表
     * @throws SQLException SQL 异常
     */
    public List<String> getMappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
        // 从缓存中获取已映射的列名列表，key 是 resultMap 和 columnPrefix 的组合
        List<String> mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
        // 如果缓存中没有，则需要加载
        if (mappedColumnNames == null) {
            // 加载并缓存已映射和未映射的列名
            loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
            // 再次从缓存中获取已映射的列名列表
            mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
        }
        // 返回已映射的列名列表
        return mappedColumnNames;
    }

    /**
     * 获取未映射的列名列表
     *
     * @param resultMap    ResultMap，描述对象属性和数据库列的映射关系
     * @param columnPrefix 列名前缀（用于多表查询时区分不同表的列名）
     * @return 未映射列名列表
     * @throws SQLException SQL 异常
     */
    public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
        // 从缓存中获取未映射的列名列表，key 是 resultMap 和 columnPrefix 的组合
        List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
        // 如果缓存中没有，则需要加载
        if (unMappedColumnNames == null) {
            // 加载并缓存已映射和未映射的列名
            loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
            // 再次从缓存中获取未映射的列名列表
            unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
        }
        // 返回未映射的列名列表
        return unMappedColumnNames;
    }

    /**
     * 生成缓存用的 key，由 ResultMap 的 id 和前缀拼接而成
     *
     * @param resultMap    ResultMap
     * @param columnPrefix 列名前缀
     * @return key 字符串
     */
    private String getMapKey(ResultMap resultMap, String columnPrefix) {
        return resultMap.getId() + ":" + columnPrefix;
    }

    /**
     * 给列名集合加上前缀（通常用于多表查询时区分不同表的列）
     *
     * @param columnNames 列名集合（Set 集合，元素为列名字符串）
     * @param prefix      前缀字符串（如表别名，可能为 null）
     * @return 加前缀后的列名集合（Set 集合，元素为前缀+列名）
     */
    private Set<String> prependPrefixes(Set<String> columnNames, String prefix) {
        // 如果列名集合为空，或者前缀为空/长度为0，直接返回原始列名集合
        if (columnNames == null || columnNames.isEmpty() || prefix == null || prefix.length() == 0) {
            return columnNames;
        }
        // 创建一个新的 Set 集合，用于存放加了前缀的列名
        final Set<String> prefixed = new HashSet<>();
        // 遍历每一个列名
        for (String columnName : columnNames) {
            // 将前缀和列名拼接起来，加入新集合
            prefixed.add(prefix + columnName);
        }
        // 返回加前缀后的列名集合
        return prefixed;
    }

}
